
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 
;                            CPURDMSR
;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 
;    Some 386+ CPUs support new instructions, Read and Write
;    Machine Specific Registers (RDMSR and WRMSR).  These access
;    special functions for the CPU.  The instruction is called
;    with ECX set to the register to look at.  This test program
;    looks through every possible combination to see which
;    if any registers are used by the CPU.  Any found registers
;    are indicated with the value returned in EDX:EAX.
;
;    Use "CPURDMSR +"  to display every test (runs about 30 times
;    slower that default of only displaying once every 65,536
;    tests.
;
;    (c) Copyright 1994  Frank van Gilluwe  All Rights Reserved. 

include undocpc.inc


cseg    segment para public
        assume  cs:cseg, ds:cseg, ss:stacka

;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ DATA AREA ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ

; ÄÄÄÄÄ general data

        db      'CPURDMSR v1.00 '
        db      '(c) 1994 Frank van Gilluwe',0

cpuhead db      CR, LF, CR, LF
        db      'ANALYSIS LOOKS FOR UNDOCUMENTED '
        db      'MACHINE SPECIFIC REGS   v1.00 (c) 1994 FVG'
        db      CR, LF
        db      'ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ'
        db      'ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ'
        db      CR, LF, CR, LF, '$'



below286  db    'No checks made.'
          db    '  CPU must be 386 or later for this test.'
          db    CR, LF, '$'

notreal   db    'No checks made.'
          db    '  CPU must be in real mode for this test.'
          db    CR, LF, '$'

aborted   db    CR, LF, CR, LF
          db    'Test aborted due to Ctrl-Break.'
          db    CR, LF, '$'

cpurdmsr1 db    'Read Machine Specific Register tests '
          db    'opcode 0Fh, 32h.  Use Ctrl-Break to', CR, LF
          db    'stop test.   The routine updates the '
          db    'display once every 65,536 tests.   ', CR, LF 
          db    'Use the command line option CPURDMSR '
          db    '+ to update the display on every ECX ', CR, LF 
          db    'test value (runs 30 times slower).'
          db    CR, LF, CR, LF
          db    'ECX value for RDMSR test', LF, '$'


opcodeok  db    ' Register is valid!  edx:eax='
opcodeval db    '00000000:00000000h'
          db    CR, LF, '$'

nonefound db    CR, LF
          db    'No hidden machine specific registers found '
          db    'on this CPU.', CR, LF, '$'


cpurdmsr2 db    CR
cpuecxh   db    '0000'
cpuecxl   db    '0000h$'

cpu_val  db     0                  ; CPU value from CPUVALUE
                                   ;   0 = 8088/8086 or V20/V30
                                   ;   1 = 80186/80188
                                   ;   2 = 80286
                                   ;   3 = 80386
                                   ;   4 = 80486
                                   ;   5 = Pentium

cpu_info db     0                  ; flags from CPUVALUE
                                   ;   bit 0 = 1 if CPUID ok 
                                   ;       1 = 1 if V20/V30 CPU

cpu_prot db     0                  ; protected state
                                   ;   0 = no protected mode 
                                   ;   1 = real mode
                                   ;   2 = protected mode
                                   ;   3 = V86 mode

cpu_priv db     0                  ; privilege level 0-3

cmd_line db     0                  ; "+" = only show successes

old_int6_seg dw 0                  ; temp storage for old int 6
old_int6_off dw 0                  ;  vector (bad opcode)
old_intD_seg dw 0                  ; temp storage for old int D
old_intD_off dw 0                  ;  vector (protection fault)

badoff       dw 0                  ; temp return offset if bad 
                                   ;  opcode interrupt 6 called

int_mask     db 0                  ; save area for interrupt mask

savedss      dw 0                  ; temporary saved SS
savedsp      dw 0                  ; temporary saved SP

anyfound     db 0                  ; =1 if any instructions found


;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ CODE START ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ

.286

cpurdmsr proc    far

start:  
        cmp     byte ptr ds:[80h], 1  ; any option ?
        jbe     cf_skp0            ; jump if none
        mov     bl, ds:[82h]       ; get option on cmd line
        mov     cs:[cmd_line], bl  ; save option
cf_skp0:
        mov     ax, cs
        mov     ds, ax
        mov     es, ax
        OUTMSG  cpuhead            ; output header message

; get cpu type and cpumode, and save results

        call    cpuvalue           ; get CPU type 0 to 5 in al
        mov     [cpu_val], al      ; save
        mov     [cpu_info], ah     ; save flags
        call    cpumode            ; get current mode V86, real,
                                   ;   protected into ax
        mov     [cpu_prot], al     ; save type of protection
        mov     [cpu_priv], ah     ; save privledge level

        cmp     [cpu_val], 3       ; 386 or later ?
        jae     cf_skp1
        jmp     cf_exit1           ; exit if below 386

cf_skp1:
        cmp     [cpu_prot], 2      ; real mode ?
        jb      cf_skp2            ; jump if so
        jmp     cf_exit2           ; exit if not real mode

; set up control-break, since this routine can take awhile

cf_skp2:
        push    ds
        mov     ax, cs
        mov     ds, ax
        mov     dx, offset cf_cntrl_break
        mov     ax, 2523h          ; set interrupt 23h
        int     21h
        pop     ds

        mov     ax, 3301h          ; set ctrl-break checking on
        mov     dl, 1
        int     21h

; set up bad-opcode intercept and general protection fault

        mov     ax, offset ct_bad       ; goto ct_bad if int occurs
        mov     [badoff], ax
        call    hook_int6
        call    hook_intD

; On a 80386 or later, test the Read Model Specific Register RDMSR

.386
        OUTMSG  cpurdmsr1
        mov     ecx, 0             ; start with zero 

cf_nextecx:
        cmp     [cmd_line], '+'    ; maximum display output ?
        jne     cf_skp3            ; jump if not
        OUTMSG  cpurdmsr2
cf_skp3:
        call    ct_testit          ; go test the instruction &
                                   ;   display result
        inc     ecx
        jecxz   cf_wrapup          ; exit if done

cf_update:                         ; update upper word value
        cmp     cx, 0              ; did it just wrap ?
        jne     cf_skp4            ; jump if not
        mov     eax, ecx
        ror     eax, 16
        push    ax
        mov     al, ah
        call    hex2ascii          ; convert 4nd byte to ascii
        mov     word ptr [cpuecxh], bx  ; insert in message
        pop     ax
        call    hex2ascii          ; convert 3rd byte to ascii
        mov     word ptr [cpuecxh+2], bx  ; insert in message
        jmp     cf_skp5            ; do lower and output

cf_skp4:
        cmp     [cmd_line], '+'    ; maxumum display output ?
        jne     cf_nextecx         ; jump if not
cf_skp5:
        mov     al, ch  
        call    hex2ascii          ; convert 2nd byte to ascii
        mov     word ptr [cpuecxl], bx  ; insert in message
        mov     al, cl  
        call    hex2ascii          ; convert 1st byte to ascii
        mov     word ptr [cpuecxl+2], bx  ; insert in message
        OUTMSG  cpurdmsr2
        jmp     cf_nextecx


; comes here if cntrl-break pressed

cf_cntrl_break:
        mov     [anyfound], 2      ; set flag that test aborted

; done with tests - restore interrupt 6 to original vector

cf_wrapup:
        call     restore_int6
        call     restore_intD

        cmp     [anyfound], 1      ; were instructions found?
        je      cf_exit
        cmp     [anyfound], 2      ; aborted ?
        je      cf_exit0           ; jump if so
        OUTMSG  nonefound          ; output none found message
        jmp     cf_exit            ; done!

cf_exit0:
        OUTMSG  aborted
        jmp     cf_exit            ; done!

cf_exit1:
        OUTMSG  below286           ; no test for CPU
        jmp     cf_exit            ; done!

cf_exit2:
        OUTMSG  notreal            ; no test if not real mode

cf_exit:
        mov     ah,4Ch
        int     21h                ; exit with al return code
cpurdmsr endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    TEST IF INSTRUCTION IS VALID
;       Test RDMSR
;
;       Called with:    ecx = register
;                       [cmd_line] = "+" when every test shown
;
;       Returns:        Message about success or failure

ct_testit proc  near
        push    di


        pushad
        push    ds
        push    es
        cli
        mov     [savedss], ss
        mov     [savedsp], sp
        push    ax                 ; push garbage onto stack
        push    ax                 ;  in case instruction pops
        push    ax                 ;  something from the stack
        push    ax

; The instruction to test 

        db      0Fh, 32h

; only get here if instruction was not a bad opcode!

        cli
        mov     ss, cs:[savedss]   ; restore registers
        mov     sp, cs:[savedsp]
        pop     es
        pop     ds

; insert EDX:EAX value into output line

        push    ecx
        push    eax
        mov     eax, edx
        mov     di, offset opcodeval
        mov     cx, 4              ; four bytes to convert
ct_loop1:
        rol     eax, 8             ; get byte
        call    hex2ascii
        mov     [di], bx
        add     di, 2
        loop    ct_loop1

        inc     di                 ; skip over colon
        pop     eax
        mov     cx, 4              ; four bytes to convert
ct_loop2:
        rol     eax, 8             ; get byte
        call    hex2ascii
        mov     [di], bx
        add     di, 2
        loop    ct_loop2
        pop     ecx

        cmp     [cmd_line], '+'    ; are we showing every test?
        je      ct_skp1            ; jump if so

        mov     al, ch             ; get test register value
        call    hex2ascii          ; convert 2nd byte to ascii
        mov     word ptr [cpuecxl], bx  ; insert in message
        mov     al, cl  
        call    hex2ascii          ; convert 1st byte to ascii
        mov     word ptr [cpuecxl+2], bx  ; insert in message
        OUTMSG  cpurdmsr2
ct_skp1:
        OUTMSG  opcodeok           ; success message
        mov     [anyfound], 1      ; flag at least one found
        popad
        sti                        ; enable interrupts
        jmp     ct_skp3

; int 6, bad opcode returns here if bad opcode detected
; int D, protection fault returns here if fault occurs

ct_bad:
        cli
        mov     ss, cs:[savedss]
        mov     sp, cs:[savedsp]
        pop     es
        pop     ds
        popad
        sti                        ; enable interrupts

ct_skp3:
        pop     di
        ret
ct_testit endp

;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    HOOK INTERRUPT 6
;       Save the old interrupt 6 vector and replace it with  
;       a new vector to the bad_op_handler.  Vectors are handled 
;       directly without using DOS.
;
;       Called with:    nothing
;                      
;       Returns:        vector hooked
;                       old vector stored at
;                         ds:[old_int6_seg]
;                         ds:[old_int6_off]
;
;       Regs used:      none

hook_int6 proc    near
        push    ax
        push    cx
        push    es
        xor     ax, ax
        mov     es, ax
        cli                        ; disable interrupts
        mov     ax, es:[6*4]       ; get offset of int 6
        mov     cx, es:[6*4+2]     ; get segment
        mov     word ptr es:[6*4], offset bad_op_handler
        mov     word ptr es:[6*4+2], seg bad_op_handler
        sti                        ; enable interrupts
        mov     [old_int6_seg], cx ; save original vector
        mov     [old_int6_off], ax
        pop     es
        pop     cx
        pop     ax
        ret
hook_int6 endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    RESTORE INTERRUPT 6
;       Restore the previously saved old interrupt 6 vector.
;       Vectors handled directly without using DOS.
;
;       Called with:    old vector stored at
;                         ds:[old_int6_seg]
;                         ds:[old_int6_off]
;                      
;       Returns:        vector restored
;
;       Regs used:      none

restore_int6 proc    near
        push    ax
        push    cx
        push    dx
        mov     cx, [old_int6_seg] ; get original vector
        mov     dx, [old_int6_off]
        push    es
        xor     ax, ax
        mov     es, ax
        cli                        ; disable interrupts
        mov     es:[6*4], dx       ; restore original int 6
        mov     es:[6*4+2], cx
        sti                        ; enable interrupts
        pop     es
        pop     dx
        pop     cx
        pop     ax
        ret
restore_int6 endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    BAD OFFSET INTERRUPT HANDLER
;       If a bad opcode occurs (80286 or later) will come here.
;       The saved BADOFF offset is used to goto the routine
;       previously stored in BADOFF.
;
;       In a few cases, it is also used for double faults. A few
;       instructions (RDMSR & WRMSR) can issue a double fault if
;       not supported, so well come here as well.
;
;       Called with:    cs:[badoff] previously set
;
;       Returns:        returns to address stored in badoff


bad_op_handler proc far
        push    ax
        push    bp
        mov     ax, cs:[badoff]
        mov     bp, sp
        mov     ss:[bp+4], ax      ; insert new return offset
        pop     bp
        pop     ax
        iret
bad_op_handler endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    HOOK INTERRUPT D
;       Save the current interrupt mask state, and mask off
;       interrupt D (General Protection Fault).  Also save
;       the old interrupt D vector and replace it with  a new 
;       vector to bad_op_handler.  Vectors are handled 
;       directly without using DOS.  
;
;       NOTE: This is only effective if in Real Mode.  If in 
;       V86 mode, the memory manager will not pass the fault 
;       into this routine.
;
;       Called with:    nothing
;                      
;       Returns:        vector hooked
;                       old vector stored at
;                         ds:[old_intD_seg]
;                         ds:[old_intD_off]
;                       interrupt mask stored at
;                         ds:[int_mask]
;
;       Regs used:      none

hook_intD proc    near
        push    ax
        push    cx
        push    es
        xor     ax, ax
        mov     es, ax
        cli                        ; disable interrupts

        in      al, 21h            ; get current interrupt mask
        mov     [int_mask], al     ; save
        IODELAY
        or      al, 20h            ; set bit to disable hardware 
        out     21h, al            ;  IRQ 5 (interrupt D)
                                   ; Now change the vector
        mov     ax, es:[0Dh*4]     ; get offset of int D
        mov     cx, es:[0Dh*4+2]   ; get segment
        mov     word ptr es:[0Dh*4], offset bad_op_handler  
        mov     word ptr es:[0Dh*4+2], seg bad_op_handler
        sti                        ; enable interrupts
        mov     [old_intD_seg], cx ; save original vector
        mov     [old_intD_off], ax
        pop     es
        pop     cx
        pop     ax
        ret
hook_intD endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    RESTORE INTERRUPT D
;       Restore the previously saved old interrupt D vector
;       and restore the interrupt mask to it's prior state
;       Vectors handled directly without using DOS.
;
;       Called with:    old vector stored at
;                         ds:[old_intD_seg]
;                         ds:[old_intD_off]
;                       interrupt mask stored at 
;                         ds:[int_mask]
;                      
;       Returns:        vector restored
;
;       Regs used:      none

restore_intD proc    near
        push    ax
        push    cx
        push    dx
        mov     cx, [old_intD_seg] ; get original vector
        mov     dx, [old_intD_off]
        push    es
        xor     ax, ax
        mov     es, ax
        cli                        ; disable interrupts
        mov     es:[0Dh*4], dx     ; restore original int D
        mov     es:[0Dh*4+2], cx
                                   ; Now restore IRQ 5 mask
        in      al, 21h            ; get current interrupt mask
        mov     ah, [int_mask]     ; get previous mask
        IODELAY
        or      ah, 0DFh           ; set all but IRQ 5 bit
        and     al, ah             ; insert old IRQ 5 state
        out     21h, al            ;  IRQ 5 (interrupt D)

        sti                        ; enable interrupts
        pop     es
        pop     dx
        pop     cx
        pop     ax
        ret
restore_intD endp


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    CPU IDENTIFICATION SUBROUTINE
;       Identify the CPU type, from 8088 to the Pentium.  Works 
;       even if the 386 or later CPU is in V86 mode.  Note that
;       interrupts are enabled at exit, even if they were 
;       disabled on entry.  If it is necessary to run this 
;       routine with interrupts disabled, just remove all CLI 
;       and STI instructions, so long as interrupts are 
;       always disabled before running.
;
;       Called with:    nothing
;
;       Returns:        al = CPU type
;                             0 if 8088/8086 or V20/V30
;                             1 if 80186/80188
;                             2 if 80286
;                             3 if 80386
;                             4 if 80486
;                             5 if Pentium
;                       ah =  bit 0 = 0 if CPUID unavailable
;                                     1 if CPUID ok
;                             bit 1 = 0 if not V20/V30
;                                     1 if NEC V20/V30
;
;       Regs used:      ax, bx (all)
;                       eax, ebx (386 or later)
;
;       Subs called:    hook_int6, restore_int6, bad_op_handler

.8086   ; all instructions 8088/8086 unless overridden later

cpuvalue proc    far
        push    cx
        push    dx
        push    ds
        push    es

; 8088/8086 test - Use rotate quirk - All later CPUs mask the CL
;   register with 0Fh, when shifting a byte by cl bits.  This 
;   test loads CL with a large value (20h) and shifts the AX
;   register right.  With the 8088, any bits in AX are shifted 
;   out, and becomes 0.  On all higher level processors, the
;   CL value of 20h is anded with 0Fh, before the shift.  This
;   means the effective number of shifts is 0, so AX is 
;   unaffected.

        mov     cl, 20h            ; load high CL value
        mov     ax, 1              ; load a non-zero value in AX
        shr     ax, cl             ; do the shift
        cmp     ax, 0              ; if zero, then 8088/86
        jne     up186              ; jump if not 8088/86

; V20/V30 test - It is now either a V20/V30 or a 8088.  I'll use
;   another undocumented trick to find out which.  On the 8088,
;   0Fh performs a POP CS.  On the V20/V30, it is the start of
;   a number of multi-byte instructions.  With the byte string
;   0Fh, 14h, C3h the CPU will perform the following:
;               8088/8086               V20/V30
;             pop     cs              set1   bl, cl  
;             adc     al, 0C3h

        xor     al, al             ; clear al and carry flag
        push    cs
        db      0Fh, 14h, 0C3h     ; instructions (see above)
        cmp     al, 0C3h           ; if al is C3h then 8088/8086
        jne     upV20
        mov     ax, 0              ; set 8088/8086 flag
        jmp     uP_Exit

upV20:
        pop     ax                 ; correct for lack of pop cs
        mov     ax, 200h           ; set V20/V30 flag
        jmp     uP_Exit

; 80186/80188 test - Check what is pushed onto the stack with a 
;   PUSH SP instruction.  The 80186 updates the stack pointer 
;   before the value of SP is pushed onto the stack.  With all
;   higher level processors, the current value of SP is pushed
;   onto the stack, and then the stack pointer is updated.
        
up186:  
        mov     bx, sp             ; save the current stack ptr
        push    sp                 ; do test
        pop     ax                 ; get the pushed value
        cmp     ax, bx             ; did SP change ?
        je      up286              ; if not, it's a 286+ 
        mov     ax, 1              ; set 80186 flag
        jmp     uP_Exit

; 80286 test A - We'll look at the top four bits of the EFLAGS 
;   register.  On a 286, these bits are always zero.  Later 
;   CPUs allow these bits to be changed.  During this test, 
;   We'll disable interrupts to ensure interrupts do not change 
;   the flags. 

up286: 
        cli                        ; disable interrupts
        pushf                      ; save the current flags

        pushf                      ; push flags onto stack 
        pop     ax                 ; now pop flags from stack
        or      ax, 0F000h         ; try and set bits 12-15 hi
        push    ax
        popf                       ; set new flags
        pushf
        pop     ax                 ; see if upper bits are 0

        popf                       ; restore flags to original
        sti                        ; enable interrupts
        test    ax, 0F000h         ; were any upper bits 1 ?
        jnz     up386              ; if so, not a 286

; 80286 test B - If the system was in V86 mode, (386 or higher) 
;   the POPF instruction causes a protection fault, and the 
;   protected mode software must emulate the action of POPF. If 
;   the protected mode software screws up, as occurs with a 
;   rarely encountered bug in Windows 3.1 enhanced mode, the 
;   prior test may look like a 286, but it's really a higher 
;   level processor. We'll check if the protected mode bit is 
;   on.  If not, it's guaranteed to be a 286.

.286P                              ; allow a 286 instruction
        smsw    ax                 ; get machine status word
        test    ax, 1              ; in protected mode ?
        jz      is286              ; jump if not (must be 286)

; 80286 test C - It's very likely a 386 or greater, but it is 
;   not guaranteed yet.  There is a small possibility the system 
;   could be in 286 protected mode so we'll do one last test. We 
;   will try out a 386 unique instruction, after vectoring the 
;   bad-opcode interrupt vector (int 6) to ourselves.  

        call    hook_int6          ; do it!
        mov     [badoff], offset upbad_op  ; where to go if bad
.386
        xchg    eax, eax           ; 32 bit nop (bad on 286)
        
        call    restore_int6       ; restore vector
        jmp     up386              ; only gets here if 386 
                                   ;  or greater!

; Interrupt vector 6 (bad opcode) comes here if system is a 
;   80286 (assuming the 286 protected mode interrupt 6 handler 
;   will execute the bad-opcode interrupt). 

upbad_op:
        call    restore_int6
is286:
        mov     ax, 2              ; set 80286 flag
        jmp     uP_Exit

; 80386 test - Bit 18 in EFLAGS is not settable on a 386, but is
;   changeable on the 486 and later CPUs.  Bit 18 is used to 
;   flag alignment faults. During this test, we'll disable 
;   interrupts to ensure no interrupt will change any flags.

.386                               ; allow 386 instructions

up386:
        cli                        ; disable interrupts
        pushfd                     ; push flags to look at
        pop     eax                ; get eflags
        mov     ebx, eax           ; save for later
        xor     eax, 40000h        ; toggle bit 18
        push    eax                                    
        popfd                      ; load modified eflags to CPU
        pushfd                     ; push eflags to look at
        pop     eax                ; get current eflags
        push    ebx                ; push original onto stack
        popfd                      ; restore original flags
        sti                        ; enable interrupts
        xor     eax, ebx           ; check if bit changed 
        jnz     up486              ; changed, so 486 or later
        mov     ax, 3              ; set 80386 flag
        jmp     uP_Exit

; 80486 test - Bit 21 in EFLAGS is not settable on a 486, but is
;   changeable on the Pentium CPU.  If bit 21 is changeable, it 
;   indicates the CPU supports the CPUID instruction.  It's 
;   amazing it's only taken 10 years to implement the CPUID 
;   instruction, which should have been included from the start!  
;   During this test, we'll disable interrupts to ensure no 
;   interrupt will change any flags. 

up486:
        cli                        ; disable interrupts
        pushfd                     ; push flags to look at
        pop     eax                ; get eflags
        mov     ebx, eax           ; save for later
        xor     eax, 200000h       ; toggle bit 21
        push    eax                                    
        popfd                      ; load modified eflags to CPU
        pushfd                     ; push eflags to look at
        pop     eax                ; get current eflags
        push    ebx                ; push original onto stack
        popfd                      ; restore original flags
        sti                        ; enable interrupts
        xor     eax, ebx           ; check if bit changed 
        jnz     upPentium          ; changed, it's a Pentium
        mov     ax, 4              ; set 80486 flag
        jmp     uP_Exit

; Pentium - It's possible the CPUID instruction may appear on 
;   other CPU chips, so run the CPUID instruction to see what 
;   CPU type it indicates.  The CPUID returns a family number 
;   0 to 5 for the processor type.  As of this date, only the 
;   Pentium supports the CPUID instruction and it is assigned 
;   type 5.

upPentium:
        push    ecx                ; CPUID changes eax to edx
        push    edx
        mov     eax, 1             ; get family info function
        CPUID                      ; macro for CPUID instruction
        and     eax, 0F00h         ; find family info
        shr     eax, 8             ; move to al
        mov     ah, 1              ; set flag that CPUID ok
        pop     edx
        pop     ecx
       
up_Exit:
        pop     es
        pop     ds
        pop     dx
        pop     cx
        ret
cpuvalue endp
.8086                              ; return to 8086 instructions


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;    CPU MODE
;       Check if the 286 or later CPU is in real, protected or
;       V86 mode.  It is assumed that if the 80386 or later
;       processor is in protected mode, we must be in V86 mode.
;
;       Call with:      ds:[cpu_val] set
;
;       Returns:        al = 0 protected mode not supported 
;                            1 if real mode 
;                            2 if protected mode
;                            3 if V86 mode
;                       ah = privilege level 0 to 3 
;
;       Regs used:      ax

.386P                              ; allow 286/386 instructions

cpumode proc    near
        push    cx
        xor     cx, cx             ; assume no protected mode
        cmp     [cpu_val], 2       ; 286 CPU or later ?
        jb      cpum_Exit          ; jump if not
        mov     cx, 1              ; assume real mode flag
        smsw    ax                 ; get machine status word
        test    ax, 1              ; in protected mode ?
        jz      cpum_Exit          ; jump if not (real mode)

cpu_not_real:
        mov     cl, 2              ; protected mode
        pushf
        pop     ax                 ; get flags
        and     ax, 3000h          ; get I/O privilege level
        shl     ax,12
        mov     ch, al             ; save privilege
        cmp     [cpu_val], 2       ; if 286, then protected
        je      cpum_Exit          ; jump if so

; On 386 or later, we have to assume V86 mode.  Note that the 
;  next four lines of code (commented out) might seem the 
;  correct way to detect V86 mode.  It will not work, since the 
;  PUSHFD instruction clears the VM bit before placing it on the 
;  stack.  This is undocumented on the 386 and 486, but 
;  documented on the Pentium. 

;        pushfd
;        pop     eax                ; get extended flags
;        test    eax, 20000h        ; V86 mode ?
;        jz      cpum_out_mode      ; jump if not

        mov     cl, 3              ; return V86 mode

cpum_Exit:
        mov     ax, cx             ; return status
        pop     cx
        ret
cpumode endp
.8086


;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
;   HEX2ASCII
;       Convert the hex number in al into two ascii characters
;
;       Called with:    al = input hex number
;
;       Returns         bx = converted digits in ascii
;
;       Regs Used:      al, bx

hex2ascii       proc    near
        mov     bl, al
        and     al, 0fh
        add     al, 90h
        daa
        adc     al, 40h
        daa
        mov     bh, al

        mov     al, bl             ; upper nibble
        shr     al, 1
        shr     al, 1
        shr     al, 1
        shr     al, 1
        and     al, 0fh
        add     al, 90h
        daa
        adc     al, 40h
        daa
        mov     bl, al             ; bx has two ascii bytes
        ret
hex2ascii       endp



cseg    ends

;================================================== stack ======

stacka  segment para stack

        db      192 dup (0)

stacka  ends

        end     start



